package org.smog.core.config.web;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.smog.core.config.security.SecurityConstant;
import org.smog.core.config.web.model.ErrorResponse;
import org.smog.core.exception.ApplicationException;
import org.smog.core.exception.BaseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 全局异常处理
 * <p>
 * ErrorController可对全局错误进行处理，但是其获取不到异常的具体信息，同时也无法根据异常类型进行不同的响应，例如对自定义异常的处理
 * 而@ControllerAdvice可对全局异常进行捕获，包括自定义异常
 * 需要清楚的是，其是应用于对springmvc中的控制器抛出的异常进行处理，而对于404这样不会进入控制器处理的异常不起作用，所以此时还是要依靠ErrorController来处理
 *
 * @Description: Created by IntelliJ IDEA.
 * @project_name: smogTemp
 * @time: 2020-05-15 15:23
 * @email: 17685306043@163.com
 * @author: huangZhongYao
 */
@Slf4j
@Controller
public class ExceptionHandlerController implements ErrorController {

	private static final String ERROR_PATH = "/error";

	private ErrorAttributes errorAttributes;

	@Autowired
	public ExceptionHandlerController(ErrorAttributes errorAttributes) {
		this.errorAttributes = errorAttributes;
	}

	@Override
	public String getErrorPath() {
		return ERROR_PATH;
	}

	/**
	 * web页面错误处理
	 */
	@RequestMapping(value = ERROR_PATH, produces = "text/html")
	@ResponseBody
	public String errorPageHandler(HttpServletRequest request, HttpServletResponse response) {
		ErrorResponse errorResponse = buildErrorResponse(request, response);

		return JSON.toJSONString(errorResponse);
	}

	/**
	 * 除web页面外的错误处理，比如json/xml等
	 */
	@RequestMapping(value = ERROR_PATH)
	@ResponseBody
	public ErrorResponse errorApiHandler(HttpServletRequest request, HttpServletResponse response) {
		return buildErrorResponse(request, response);
	}

	/**
	 * 自定义异常处理
	 *
	 * @param request
	 * @param response
	 * @return
	 */
	private ErrorResponse buildErrorResponse(HttpServletRequest request, HttpServletResponse response) {
		ServletWebRequest webRequest = new ServletWebRequest(request);
		Throwable throwable = this.errorAttributes.getError(webRequest);
		Map<String, Object> attr = this.errorAttributes.getErrorAttributes(webRequest, false);
		ErrorResponse errorResponse = new ErrorResponse();
		int statusCode = Integer.parseInt(attr.get("status").toString());

		if (throwable instanceof BaseException) {
			// 自定义异常处理
			errorResponse.setCode(((BaseException) throwable).getCode());
			errorResponse.setMsg(((BaseException) throwable).getMessage());
		} else if (throwable instanceof BindException) {
			// 绑定异常 validation 验证异常
			ApplicationException exception = new ApplicationException(throwable);
			errorResponse.setCode(exception.getCode());
			List<ObjectError> allErrors = ((BindException) throwable).getAllErrors();
			String allErrorsStr = allErrors.stream()
					.map(DefaultMessageSourceResolvable::getDefaultMessage)
					.collect(Collectors.joining(";"));
			errorResponse.setMsg(allErrorsStr);
		} else if (throwable instanceof MethodArgumentNotValidException) {
			// 绑定验证异常
			ApplicationException exception = new ApplicationException(throwable);
			errorResponse.setCode(exception.getCode());
			List<ObjectError> allErrors = ((MethodArgumentNotValidException) throwable).getBindingResult().getAllErrors();
			String allErrorsStr = allErrors.stream()
					.map(DefaultMessageSourceResolvable::getDefaultMessage)
					.collect(Collectors.joining(";"));
			errorResponse.setMsg(allErrorsStr);
		} else if (SecurityConstant.UNAUTHORIZED_CODE == statusCode || SecurityConstant.FORBIDDEN_CODE == statusCode) {
			errorResponse.setCode(statusCode);
			errorResponse.setMsg(throwable.getMessage());
		} else if (null != throwable) {
			Exception exception = (Exception) throwable;
			Class<? extends Exception> aClass = exception.getClass();
			errorResponse.setMsg(aClass.getName());
			errorResponse.setCode(statusCode);
		}

		errorResponse.setPath(attr.get("path").toString());
		this.setHttpStatusOk(response);
		return errorResponse;
	}

	private void setHttpStatusOk(HttpServletResponse response) {
		response.setStatus(HttpStatus.OK.value());
	}
}
